#pragma once
#ifndef __GLF_BYTEARRAY_H__
#define __GLF_BYTEARRAY_H__

#include <glf/core/types.h>
#include <vector>
#include <string.h>

#include <string>

namespace glf
{

	// ByteArray
	struct ByteArray
	{
	public:
		virtual ~ByteArray() {}
		bool Empty() const { return Size() == 0; }
		virtual const uchar* GetData() = 0;
		virtual const uchar* const GetData() const = 0;		
		virtual uint Size() const = 0;

	public:
		bool operator ==(const ByteArray& stream) const
		{
			if (Size() != stream.Size())
			{
				return false;
			}
			if (Empty() && stream.Empty())
			{
				return true;
			}			
			return memcmp(GetData(), stream.GetData(), Size()) == 0;
		}
		bool operator !=(const ByteArray& stream) const
		{
			return !(*this == stream);
		}
	};

	//ByteArrayWriter
	template <class UCHAR_ALLOCATOR = std::allocator<uchar> >
	class ByteArrayWriter : public ByteArray
	{
	public:

		ByteArrayWriter()
			: mPos(0), mSize(0)
		{
		}

		ByteArrayWriter(const ByteArray& stream)
			: mPos(0), mSize(0)
		{
			if (!stream.Empty())
			{
				Write(stream.GetData(), stream.Size());
			}
		}

		void Clear()
		{
			mData.clear();
			mPos = 0;
			mSize = 0;
		}

		void Reserve(uint bytes)
		{
			mData.reserve(bytes);
		}

		ByteArrayWriter& Write(const void* d, uint len)
		{
			if (len > 0)
			{
				if(mPos + len > mData.size())
				{
					mData.resize(mPos + len);
				}
				const uchar* ptr = (const uchar*)d;
				memcpy(&mData[0] + mPos, ptr, len);
				mPos += len;
				mSize = glf::Max(mSize, mPos);
			}
			return *this;
		}

		ByteArrayWriter& Write(const char* s)
		{
			Write(s, strlen(s));
			Write("\0", 1);
			return *this;
		}

		ByteArrayWriter& Write(uint64 i)
		{
			Write((uchar)(i & 0xff));
			Write((uchar)((i >> 8) & 0xff));
			Write((uchar)((i >> 16) & 0xff));
			Write((uchar)((i >> 24) & 0xff));

			Write((uchar)((i >> 32) & 0xff));
			Write((uchar)((i >> 40) & 0xff));
			Write((uchar)((i >> 48) & 0xff));
			return Write((uchar)((i >> 56) & 0xff));
		}

		ByteArrayWriter& Write(uint i)
		{
			Write((uchar)(i & 0xff));
			Write((uchar)((i >> 8) & 0xff));
			Write((uchar)((i >> 16) & 0xff));
			return Write((uchar)((i >> 24) & 0xff));
		}

		ByteArrayWriter& Write(int i)
		{
			Write((uchar)(i & 0xff));
			Write((uchar)((i >> 8) & 0xff));
			Write((uchar)((i >> 16) & 0xff));
			return Write((uchar)((i >> 24) & 0xff));
		}

		ByteArrayWriter& Write(float i)
		{
			uint u = *(uint*)&i;
			Write((uchar)(u & 0xff));
			Write((uchar)((u >> 8) & 0xff));
			Write((uchar)((u >> 16) & 0xff));
			return Write((uchar)((u >> 24) & 0xff));
		}

		ByteArrayWriter& Write(ushort i)
		{
			Write((uchar)(i & 0xff));
			return Write((uchar)((i >> 8) & 0xff));
		}

		ByteArrayWriter& Write(short i)
		{
			Write((uchar)(i & 0xff));
			return Write((uchar)((i >> 8) & 0xff));
		}

		ByteArrayWriter& Write(uchar i)
		{
			return Write(&i, sizeof(uchar));
		}

		ByteArrayWriter& Write(bool i)
		{
			uchar value = i ? 1 : 0;
			return Write(&value, sizeof(uchar));
		}

		template <typename TYPE>
		ByteArrayWriter &WriteArray(TYPE *v, uint size)
		{
			while (size--)
			{
				Write(*(v++));
			}
			return *this;
		}

		template <typename TYPE>
		ByteArrayWriter &FillArray(TYPE v, uint size)
		{
			while (size--)
			{
				Write(v);
			}
			return *this;
		}
		
		void Seek(uint pos)
		{
			GLF_ASSERT(pos <= mSize);
			mPos = pos;
		}

		const uchar* GetData()
		{
			if (mSize)
			{
				return &mData[0];
			}
			return 0;
		}
		
		const uchar* const GetData() const
		{
			if (mSize)
			{
				return &mData[0];
			}
			return 0;
		}

		uint Size() const
		{
			return mSize;
		}

		uint Tell() const
		{
			return mPos;
		}		

	protected:

		std::vector<uchar, UCHAR_ALLOCATOR > mData;
		uint mPos;
		uint mSize;
	};

	//ByteArrayReader
	template <class CHAR_ALLOCATOR = std::allocator<char> >
	class ByteArrayReader : public ByteArray
	{
	public:
		typedef std::basic_string< char, std::char_traits<char>, CHAR_ALLOCATOR > stringBA;

	public:

		ByteArrayReader(const uchar* data, uint len)
			: mData(data)
			, mPos(data)
			, mEnd(data + len)
		{
		}

		ByteArrayReader(const ByteArray& stream)
			: mData(stream.GetData())
			, mPos(stream.GetData())
			, mEnd(stream.GetData() + stream.Size())
		{
		}

		uint Available()
		{
			return mEnd - mPos;
		}

		uint Read(uchar* buffer, uint size)
		{
			GLF_ASSERT(Available() >= size);
			memcpy(buffer, mPos, size);
			mPos += size;
			return size;
		}

		ByteArrayReader &Read(uchar &i)
		{
			i = ReadByte();
			return *this;
		}

		uchar ReadByte()
		{
			GLF_ASSERT(mPos + 1 <= mEnd);

			uchar value = *(mPos++);
			return value;
		}

		ByteArrayReader &Read(short &i)
		{
			i = ReadShort();
			return *this;
		}

		short ReadShort()
		{
			GLF_ASSERT(mPos + 2 <= mEnd);

			short value = 0;
			value |= *(mPos++);
			value |= *(mPos++) << 8;
			return value;
		}

		ByteArrayReader &Read(unsigned short &i)
		{
			//i = ReadUShort();
			i = ReadShort();
			return *this;
		}

		unsigned short ReadUShort()
		{
			GLF_ASSERT(mPos + 2 <= mEnd);

			unsigned short value = 0;
			value |= *(mPos++);
			value |= *(mPos++) << 8;
			return value;
		}

		wchar_t ReadWChar()
		{
			GLF_ASSERT(mPos + 2 <= mEnd);

			wchar_t value = 0;
			value |= *(mPos++);
			value |= *(mPos++) << 8;
			return value;
		}

		ByteArrayReader &Read(bool &i)
		{
			i = ReadBool();
			return *this;
		}

		bool ReadBool()
		{
			return ReadByte() != 0;
		}

		ByteArrayReader &Read(int &i)
		{
			i = ReadIntLE();
			return *this;
		}

		int ReadIntLE()
		{
			GLF_ASSERT(mPos + 4 <= mEnd);

			int value = 0;
			value |= *(mPos++);
			value |= *(mPos++) << 8;
			value |= *(mPos++) << 16;
			value |= *(mPos++) << 24;
			return value;
		}

		ByteArrayReader &Read(unsigned int &i)
		{
			i = ReadUIntLE();
			return *this;
		}

		unsigned int ReadUIntLE()
		{
			GLF_ASSERT(mPos + 4 <= mEnd);

			unsigned int value = 0;
			value |= *(mPos++);
			value |= *(mPos++) << 8;
			value |= *(mPos++) << 16;
			value |= *(mPos++) << 24;
			return value;
		}

		ByteArrayReader &Read(int64 &i)
		{
			i = ReadLongLE();
			return *this;
		}

		int64 ReadLongLE()
		{
			GLF_ASSERT(mPos + 4 <= mEnd);

			uint64 value = 0;
			value |= *(mPos++);
			value |= *(mPos++) << 8;
			value |= *(mPos++) << 16;
			value |= *(mPos++) << 24;

			value |= (uint64)*(mPos++) << 32;
			value |= (uint64)*(mPos++) << 40;
			value |= (uint64)*(mPos++) << 48;
			value |= (uint64)*(mPos++) << 56;
			return (int64)value;
		}

		ByteArrayReader &Read(float &f)
		{
			f = ReadFloatLE();
			return *this;
		}		

		float ReadFloatLE()
		{
			int value = ReadIntLE();
			return *(float*)&value;
		}

		float ReadFloat()
		{
			return ReadFloatLE();
		}

		ByteArrayReader &Read(stringBA &str)
		{
			str = ReadString();
			return *this;
		}

		stringBA ReadString()
		{
			stringBA str;
			while(mPos < mEnd)
			{
				char c = *(mPos++);
				if(c == 0)
					break;
				str += c;
			}
			return str;
		}

		template <typename TYPE>
		TYPE Read()
		{
			TYPE v;
			Read(v);
			return v;
		}

		template <typename TYPE>
		ByteArrayReader &ReadArray(TYPE *v, uint size)
		{
			while (size--)
			{
				Read(*(v++));
			}
			return *this;
		}

		const uchar* GetData()
		{
			return mPos;
		}

		const uchar* const GetData() const
		{
			return mPos;
		}

		void Seek(uint pos)
		{
			GLF_ASSERT(pos <= Size());
			mPos = mData + pos;
		}

		uint Size() const
		{
			return mEnd - mData;
		}

		uint Tell() const
		{
			return mPos - mData;
		}

		uint Peek(void* buffer, uint size)
		{
			const uchar* oldPos = mPos;
			uint readBytes = Read(buffer, size);
			mPos = oldPos;
			return readBytes;
		}


	protected:

		const uchar* mData;
		const uchar* mPos;
		const uchar* mEnd;
	};

	typedef ByteArrayReader<> ByteReader;
	typedef ByteArrayWriter<> ByteWriter;

}

#endif //__GLF_BYTEARRAY_H__